﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ZeroBugBounce.Missive.Locks;
using System.Runtime.InteropServices;

namespace ZeroBugBounce.Missive
{
	public class TaskParallelDispatcher : IDispatcher
	{
		Messenger messenger;
		ILock handlerLock;
		ISet<Task> tasks;

		public TaskParallelDispatcher(Messenger messenger)
		{
			this.messenger = messenger;
			handlerLock = new SpinningHybridLock(100);
			tasks = new HashSet<Task>();
		}

		public void WaitAll()
		{
			handlerLock.Enter();
			Boolean remainingTasks = tasks.Count > 0;
			handlerLock.Leave();

			while (remainingTasks)
			{
				handlerLock.Enter();
				Task[] currentTasks = tasks.ToArray();
				handlerLock.Leave();

				Task.WaitAll(currentTasks, 2000);

				handlerLock.Enter();
				remainingTasks = currentTasks.Length > 0;
				handlerLock.Leave();
			}
		}

		public void Enqueue(IDispatchContext context)
		{
			if (context.Handler.DispatchMode == DispatchMode.Asynchronous)
			{
				var task = CreateAsyncTask(context, false);
				handlerLock.Enter();
				tasks.Add(task);
				handlerLock.Leave();
				task.Start();
			}
			else if (context.Handler.DispatchMode == DispatchMode.AsyncLongRunning)
			{
				var task = CreateAsyncTask(context, true);
				handlerLock.Enter();
				tasks.Add(task);
				handlerLock.Leave();
				task.Start();
			}
			else if (context.Handler.DispatchMode == DispatchMode.Synchronous)
			{
				var task = CreateSynchronousTask(context);
				task.RunSynchronously();
			}
			else
			{
				throw new ArgumentOutOfRangeException("Dispatch mode had an invalid value");
			}
		}

		private Task CreateAsyncTask(IDispatchContext context, Boolean longRunning)
		{
		    Task task = null;
		    task = new Task(c =>
		    {
		        var exeContext = c as IDispatchContext;

		        try
		        {
		            exeContext.Execute();
		        }
		        catch (Exception ex)
		        {
		            exeContext.Exception = ex;
		            messenger.RaiseHandleError(ex, exeContext.Handler.Topic, exeContext.HandlerContext.Message);
		        }
		        finally
		        {
		            handlerLock.Enter();
		            tasks.Remove(task);
		            handlerLock.Leave();
		        }
		    },
		    context,
		    context.HandlerContext.CancellationToken,
		    longRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.None);

		    return task;
		}

		private Task CreateSynchronousTask(IDispatchContext context)
		{
			return new Task(c =>
			{
				var exeContext = c as IDispatchContext;

				try
				{
					exeContext.Execute();
				}
				catch (Exception ex)
				{
					exeContext.Exception = ex;
					messenger.RaiseHandleError(ex, exeContext.Handler.Topic, exeContext.HandlerContext.Message);
				}
			}, context, context.HandlerContext.CancellationToken);
		}
	}
}
